﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar;

namespace LightCAD.MathLib.CSG
{
    public enum TriCode { KEEP_TRI, DELETE_TRI, FLIP_TRI };
    struct BoolData
    {
        public bool is_isct;
    }

   

    partial class Mesh
    {
        public class IsctVertEdgeTriInput
        {
            public CorkVertex[] e = new CorkVertex[2];
            public CorkVertex[] t = new CorkVertex[3];
        };

       public class IsctVertTriTriTriInput
        {
            public CorkVertex[,] t = new CorkVertex[3, 3];
        };
        class BoolProblem
        {
            private Mesh mesh;
            private EGraphCache<BoolData> ecache;
            public BoolProblem(Mesh mesh)
            {
                this.mesh = mesh;
            }
            public void doSetup(Mesh rhs)
            {
                // Label surfaces...
                mesh.for_tris((tri, va, vb, vc) =>
                {
                    tri.bool_alg_data = 0;
                });
                rhs.for_tris((tri, va, vb, vc) =>
                {
                    tri.bool_alg_data = 1;
                });

                mesh.disjointUnion(rhs);
                mesh.resolveIntersections();

                populateECache();

                // form connected components;
                // we get one component for each connected component in one
                // of the two input meshes.
                // These components are not necessarily uniformly inside or outside
                // of the other operand mesh.
                var uf = new UnionFind(mesh.tris.Count);
                for_ecache((i, j, is_isct, tids) =>
                {
                    int tid0 = tids[0];
                    for (int k = 1; k < tids.Count; k++)
                        uf.unionIds(tid0, tids[k]);
                });

                // we re-organize the results of the union find as follows:
                var uq_ids = new List<int>();
                uq_ids.makeSize(mesh.tris.Count, -1);
                var components = new List<List<int>>();
                for (int i = 0; i < mesh.tris.Count; i++)
                {
                    int ufid = uf.find(i);
                    if (uq_ids[ufid] == -1)
                    { // unassigned
                        int N = components.size();
                        components.Add(new List<int>());

                        uq_ids[ufid] = uq_ids[i] = N;
                        components[N].Add(i);
                    }
                    else
                    { // assigned already
                        uq_ids[i] = uq_ids[ufid]; // propagate assignment
                        components[uq_ids[i]].Add(i);
                    }
                }

                List<bool> visited = new List<bool>();
                visited.makeSize(mesh.tris.size(), false);

                // find the "best" triangle in each component,
                // and ray cast to determine inside-ness vs. outside-ness
                for (int ind = 0; ind != components.size(); ++ind)
                {
                    // find max according to score
                    var comp = components[ind];
                    int best_tid = comp[0];
                    double best_area = 0.0;
                    // SEARCH
                    for (int ind2 = 0; ind2 != comp.size(); ++ind2)
                    {
                        var tid = comp[ind2];
                        Vec3 va = mesh.verts[mesh.tris[tid].a].pos;
                        Vec3 vb = mesh.verts[mesh.tris[tid].b].pos;
                        Vec3 vc = mesh.verts[mesh.tris[tid].c].pos;

                        double area = Utils.triArea(va, vb, vc);
                        if (area > best_area)
                        {
                            best_area = area;
                            best_tid = tid;
                        }
                    }

                    byte operand = boolData(best_tid);
                    bool inside = isInside(best_tid, operand);

                    // NOW PROPAGATE classification throughout the component.
                    // do a breadth first propagation
                    Queue<int> work = new Queue<int>();

                    // begin by tagging the first triangle
                    boolData(best_tid, (byte)(boolData(best_tid) | ((inside) ? 2 : 0)));
                    visited[best_tid] = true;
                    work.Enqueue(best_tid);

                    while (work.Count > 0)
                    {
                        var curr_tid = work.Dequeue();
                        for (int k = 0; k < 3; k++)
                        {
                            int a = mesh.tris[curr_tid][k];
                            int b = mesh.tris[curr_tid][(k + 1) % 3];
                            var entry = ecache.functor(a, b);
                            byte inside_sig = (byte)(boolData(curr_tid) & 2);
                            if (entry.data.is_isct) inside_sig ^= 2;
                            for (int indk = 0; indk != entry.tids.size(); ++indk)
                            {
                                var tid = entry.tids[indk];
                                if (visited[tid]) continue;
                                if ((boolData(tid) & 1) != operand) continue;

                                boolData(tid, (byte)(boolData(tid) | inside_sig));
                                visited[tid] = true;
                                work.Enqueue(tid);
                            }
                        }
                    }
                }

            }
            // choose what to remove
            public void doDeleteAndFlip(Func<byte, TriCode> func)
            {
                var topocache = new TopoCache(mesh);

                var toDelete = new List<Block<TopoTri>>();
                for (var tptr = topocache.tris.getFirst(); tptr != null; tptr = topocache.tris.getNext(tptr))
                {
                    TriCode code = (TriCode)(boolData(tptr.datum.@ref));
                    switch (code)
                    {
                        case TriCode.DELETE_TRI:
                            toDelete.push_back(tptr);
                            break;
                        case TriCode.FLIP_TRI:
                            topocache.flipTri(tptr.datum);
                            break;
                        case TriCode.KEEP_TRI:
                        default:
                            break;
                    }
                }

                for (var ind = 0; ind != toDelete.size(); ++ind)
                {
                    topocache.deleteTri(toDelete[ind]);
                }

                topocache.commit();
            }


            public byte boolData(int tri_id)
            {
                return mesh.tris[tri_id].data.bool_alg_data;
            }
            public void boolData(int tri_id, byte value)
            {
                mesh.tris[tri_id].data.bool_alg_data = value;
            }

            void populateECache()
            {
                ecache = mesh.createEGraphCache<BoolData>();

                // label some of the edges as intersection edges and others as not
                ecache.for_each((i, j, entry) =>
                {
                    entry.data.is_isct = false;
                    byte operand = boolData(entry.tids[0]);
                    for (int k = 1; k < entry.tids.Count; k++)
                    {
                        if (boolData(entry.tids[k]) != operand)
                        {
                            entry.data.is_isct = true;
                            break;
                        }
                    }
                });
            }

            void for_ecache(Action<int, int, bool, List<int>> action)
            {
                ecache.for_each((i, j, entry) =>
                {
                    if (entry.data.is_isct)
                    {
                        var tid0s = new List<int>();
                        var tid1s = new List<int>();
                        for (int ind = 0; ind != entry.tids.Count; ++ind)
                        {
                            var tid = entry.tids[ind];
                            if ((boolData(tid) & 1) == 1)
                                tid1s.Add(tid);
                            else
                                tid0s.Add(tid);
                        }
                        action(i, j, true, tid1s);
                        action(i, j, true, tid0s);
                    }
                    else
                    {
                        action(i, j, false, entry.tids);
                    }
                });
            }
            private bool isInside(int tid, byte operand)
            {
                // find the point to trace outward from...
                Vec3 p = new Vec3();
                p += mesh.verts[mesh.tris[tid].a].pos;
                p += mesh.verts[mesh.tris[tid].b].pos;
                p += mesh.verts[mesh.tris[tid].c].pos;
                p /= 3.0;
                // ok, we've got the point, now let's pick a direction
                Ray3 r = new Ray3();
                r.p = p;
                r.r = new Vec3(Utils.drand(0.5, 1.5), Utils.drand(0.5, 1.5), Utils.drand(0.5, 1.5));


                int winding = 0;
                // pass all triangles over ray
                for (int ind = 0; ind != mesh.tris.Count; ++ind)
                {
                    var tri = mesh.tris[ind];
                    // ignore triangles from the same operand surface
                    if ((tri.data.bool_alg_data & 1) == operand) continue;

                    double flip = 1.0;
                    int a = tri.a;
                    int b = tri.b;
                    int c = tri.c;
                    Vec3 va = mesh.verts[a].pos;
                    Vec3 vb = mesh.verts[b].pos;
                    Vec3 vc = mesh.verts[c].pos;
                    // normalize vertex order (to prevent leaks)
                    if (a > b) { Utils.swap(ref a, ref b); Utils.swap(va, vb); flip = -flip; }
                    if (b > c) { Utils.swap(ref b, ref c); Utils.swap(vb, vc); flip = -flip; }
                    if (a > b) { Utils.swap(ref a, ref b); Utils.swap(va, vb); flip = -flip; }

                    double t = 0;
                    Vec3 bary = null;
                    if (Utils.isct_ray_triangle(r, va, vb, vc, ref t, ref bary))
                    {
                        Vec3 normal = flip * Vec3.cross(vb - va, vc - va);
                        if (Vec3.dot(normal, r.r) > 0.0)
                        { // UNSAFE
                            winding++;
                        }
                        else
                        {
                            winding--;
                        }
                    }
                }

                // now, we've got a winding number to work with...
                return winding > 0;
            }
        }
    }
}
